using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Interpolation2D
{
    class Transformer
    {
        public double XminReal { get; set; }
        public double XmaxReal { get; set; }
        public double YminReal { get; set; }
        public double YmaxReal { get; set; }
        public double XminWin { get; set; }
        public double XmaxWin { get; set; }
        public double YminWin { get; set; }
        public double YmaxWin { get; set; }
        public bool Optimaze { get; set; }

        #region === private ===

        static private PointF ConvertToPointF(double xReal, double yReal)
        {
            return new PointF(Convert.ToSingle(xReal), Convert.ToSingle(yReal));
        }

        private PointF ConvertToWin(DoublePrecisionPoint dpp)
        {
            double xWin = (XmaxWin - XminWin) / (XmaxReal - XminReal) * (dpp.X - XminReal) + XminWin;
            double yWin = (YminWin - YmaxWin) / (YmaxReal - YminReal) * (dpp.Y - YminReal) + YmaxWin;

            return ConvertToPointF(xWin, yWin);
        }

        static public PointF ConvertToWin(DoublePrecisionPoint dpp, RectangleF boundRect, double MinX, double MaxX, double MinY, double MaxY)
        {
            double xWin = boundRect.Width / (MaxX - MinX) * (dpp.X - MinX) + boundRect.Left;
            double yWin = -boundRect.Height / (MaxY - MinY) * (dpp.Y - MinY) + boundRect.Bottom;

            return ConvertToPointF(xWin, yWin);
        }

        static public PointF ConvertToWin(double x, double y, RectangleF boundRect, double MinX, double MaxX, double MinY, double MaxY)
        {
            double xWin = boundRect.Width / (MaxX - MinX) * (x - MinX) + boundRect.Left;
            double yWin = -boundRect.Height / (MaxY - MinY) * (y - MinY) + boundRect.Bottom;

            return ConvertToPointF(xWin, yWin);
        }

        private DoublePrecisionPoint ConvertToDpp(PointF pf)
        {
            return new DoublePrecisionPoint { X = Convert.ToDouble(pf.X), Y = Convert.ToDouble(pf.Y) };
        }

        /// <summary>
        /// Метод для вычисления расстояние от прямой, задаваемой точками point1 и point2 до точки testPoint.
        /// </summary>
        /// <param name="point1">Первая точка, для задания прямой.</param>
        /// <param name="point2">Вторая точка, для задания прямой.</param>
        /// <param name="testPoint">Тестовая точка, расстояние до которой замеряем.</param>
        /// <returns>Расстояние расстояние от прямой, задаваемой точками point1 и point2 до точки testPoint.</returns>
        private double CalculateDistance(DoublePrecisionPoint point1, DoublePrecisionPoint point2, DoublePrecisionPoint testPoint)
        {
            DoublePrecisionPoint directVector = point2 - point1;

            double A = directVector.Y;
            double B = -directVector.X;
            double C = -A * point1.X - B * point1.Y;

            return Math.Abs(A * testPoint.X + B * testPoint.Y + C) / directVector.Module;
        }

        #endregion

        /// <summary>
        /// Преобразование точек из реальной систему координат в оконную.
        /// </summary>
        /// <param name="curve">список точек</param>
        /// <returns>Массив PointF[] для рисования кривой в оконной системе координат.</returns>
        public PointF[] Transform(Curve curve)
        {
            List<PointF> points = new List<PointF>
            {
                // первую точку добавляем всегда
                ConvertToWin(curve.GetPoint(0))
            };

            const double c_distance = 6.0;
            const double с_deviation = 0.5;

            for (int i = 1; i < curve.Count - 1; i++)
            {
                DoublePrecisionPoint dppCur = ConvertToDpp(ConvertToWin(curve.GetPoint(i)));
                DoublePrecisionPoint dppPrev = ConvertToDpp(points[points.Count - 1]);

                // добавляем новую точку только если расстояние от предыдущей стало больше чем c_distance
                if ((dppCur - dppPrev).Module > c_distance)
                {
                    if (!Optimaze)
                    {
                        points.Add(ConvertToPointF(dppCur.X, dppCur.Y));
                    }
                    else
                    {
                        if (points.Count > 1)
                        {
                            DoublePrecisionPoint dpp1 = dppPrev;
                            DoublePrecisionPoint dpp2 = ConvertToDpp(points[points.Count - 2]);

                            // добавляем точку, только если ее отклонение от прямой более чем с_deviation
                            if (CalculateDistance(dpp1, dpp2, dppCur) > с_deviation)
                            {
                                points.Add(ConvertToPointF(dppCur.X, dppCur.Y));
                            }
                        }
                        else
                        {
                            points.Add(ConvertToPointF(dppCur.X, dppCur.Y));
                        }
                    }
                }
            }

            // последнюю точку добавляем всегда
            points.Add(ConvertToWin(curve.GetPoint(curve.Count - 1)));

            return points.ToArray();
        }
    }
}

